package org.rich.charlesmurphy.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.ddevil.data.BeanData;
import org.ddevil.data.Internal;
import org.ddevil.data.util.DataUtils;
import org.springframework.util.StringUtils;

/**
 * Statistics about a particular resource.
 * One to one with each resource.
 * @author Rich
 */
public class ResourceStats extends BeanData {
	private static final long serialVersionUID = 6250889063615397624L;

	private Resource resource;
	private String parameterNames;
	// all requests for this resource.
	@Internal
	private List<Request> requests;
	@Internal
	//source groups, the group that the request for this resource was a part of
	private List<RequestGroup> sources;
	@Internal
	private CacheController avgSizeCache;
	@Internal
	private CacheController sourceSizeCache;

	@Internal
	// only makes sense for PAGE requests
	private CacheController maxGenRequests;

	/**
	 * @param resource
	 */
	public ResourceStats(Request request, RequestGroup source) {
		this.resource = new Resource(request);
		requests = new ArrayList<Request>();
		avgSizeCache = new CacheController(requests);
		maxGenRequests = new CacheController(requests);
		sources = new ArrayList<RequestGroup>();
		sourceSizeCache = new CacheController(sources);
		addSample(request, source);
	}

	public Resource getResource() {
		return resource;
	}

	/**
	 * return a list of all the parameter names used when requesting this resource.
	 * @return
	 */
	public String getParameterNames() {
		return parameterNames;
	}
	private void buildParamNames() {
		Set<String> paramNames = new HashSet<String>();
		for (Request req : requests) {
			if (StringUtils.hasText(req.getQuery())) {
				String[] nvPairs = req.getQuery().split("&");
				for (String nv : nvPairs) {
					paramNames.add(nv.split("=")[0]);
				}
			}
		}
		parameterNames = DataUtils.collectionToString(paramNames);
	}

	/**
	 *
	 * @param r the particular request for this resouce(the sample)
	 * @param source the source of r,
	 * 	i.e. if 'home.htm' generated the request(r) for this resource then 'home.htm' is the source.
	 */
	public void addSample(Request r, RequestGroup source) {
		requests.add(r);
		sources.add(source);
		buildParamNames();
	}

	public int getSampleSize() {
		return requests.size();
	}

	public int getSourceCount() {
		return sources.size();
	}
	public int getDistinctSourceCount() {
		Long cachedValue = sourceSizeCache.getCachedValue();
		if (cachedValue != null) {
			return cachedValue.intValue();
		}
		int count = 0;
		Set<String> seenEm = new HashSet<String>();
		for (RequestGroup rg : sources) {
			if (!seenEm.contains(rg.getPath())) {
				seenEm.add(rg.getPath());
				count++;
			}
		}
		sourceSizeCache.cacheValue(new Long(count));
		return count;
	}

	public int getMaxGeneratedRequests() {
		if (resource.getResourceType() != ResourceType.PAGE) {
			return 0;
		}
		Long cache = maxGenRequests.getCachedValue();
		if (cache != null) {
			return cache.intValue();
		}
		long max = 0;
		for (RequestGroup rg : sources) {
			max = Math.max(max, rg.getRequests());
		}
		maxGenRequests.cacheValue(max);
		return (int)max;
	}

	public List<RequestGroup> getSources() {
		return sources;
	}

	public long getAverageSize() {
		Long cached = avgSizeCache.getCachedValue();
		if (cached != null) {
			return cached;
		}
		long sum = 0;
		long samples = 0;
		for (Request r : requests) {
			long s = getSize(r);
			if (s != -1) {
				sum += s;
				samples++;
			}
		}
		long result =samples > 0 ? sum / samples : samples;
		avgSizeCache.cacheValue(cached);
		return result;
	}

	/*
	 * determine the size of the response.
	 * -1 indicates cached or no size data,
	 * so it won't be included in the average calculation
	 */
	private long getSize(Request request) {
		long rv = -1;
		if (StatusCode.fromCode(request.getStatus()) != StatusCode.CACHED) {
			Long size = request.getResponseBodySize();
			if (size != null) {
				rv = size;
			}
		}
		return rv;
	}

	public static class CacheController {
		private Long cachedValue = null;
		private int lastSize;
		private Collection<?> watchCollection;
		public CacheController(Collection<?> watchCollection) {
			this.watchCollection = watchCollection;
		}
		public Long getCachedValue() {
			if (cachedValue == null || watchCollection == null) {
				return null;
			}
			if (lastSize == watchCollection.size()) {
				return cachedValue;
			}
			return null;
		}
		public synchronized void cacheValue(Long value) {
			cachedValue = value;
			lastSize = watchCollection.size();
		}
	}
}
